Skip to content

Add task_meta parameter to read_resource() for explicit task control#2750

Merged
jlowin merged 2 commits intomainfrom
add-resource-task-meta-parameter
Dec 26, 2025
Merged

Add task_meta parameter to read_resource() for explicit task control#2750
jlowin merged 2 commits intomainfrom
add-resource-task-meta-parameter

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Dec 26, 2025

Extends the task_meta parameter pattern to both tools and resources, providing explicit control over sync vs task execution with type-safe return types.

Previously, the return type of read_resource() and call_tool() was a union type requiring runtime isinstance() checks. Now, overloads narrow the type based on whether task_meta is provided:

# Synchronous execution - type is ResourceResult
result = await server.read_resource("data://test")
content = result.contents[0].content  # No isinstance check needed

# Task execution - type is CreateTaskResult  
task_result = await server.read_resource("data://test", task_meta=TaskMeta())
task_id = task_result.task.taskId  # No isinstance check needed

Key changes:

  • read_resource() and call_tool() accept optional task_meta parameter with overloads for type narrowing
  • MCP routes (_call_tool_mcp, _read_resource_mcp) no longer set fn_key - components enrich it themselves
  • Each component enriches fn_key in its _run()/_read() method using self.key as a fallback
  • Provider wrappers enrich fn_key with the parent's namespaced key before delegating to child servers
  • Uses dataclasses.replace() for immutable TaskMeta updates
  • Added tests for mounted components with direct task_meta parameter usage

Adds the same task_meta parameter pattern to resources that was added
to tools, enabling explicit control over sync vs task execution with
type-safe return types via overloads.
@marvin-context-protocol marvin-context-protocol Bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. server Related to FastMCP server implementation or server-side functionality. labels Dec 26, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 26, 2025

Walkthrough

Adds TaskMeta-aware overloads and runtime handling across the read/run pipeline: Resource, ResourceTemplate, FunctionResourceTemplate, Tool, provider wrappers, and FastMCP.read_resource now accept an optional TaskMeta and return either synchronous results or CreateTaskResult for background-task creation. Implementations enrich task_meta.fn_key when missing, pass task_meta (with explicit arguments=None where used) into check_background_task and downstream delegations, and fall back to synchronous execution (calling read/run and converting results) when no background task is scheduled.

Possibly related PRs

  • jlowin/fastmcp PR 2663: Touches the same component-level background-task routing and replaces key-based routing with TaskMeta propagation in Resource._read and related paths.
  • jlowin/fastmcp PR 2749: Introduces TaskMeta propagation and overloads for Tool._run and provider call paths, matching the propagation/enrichment changes here.
  • jlowin/fastmcp PR 2734: Modifies Resource._read, template/provider wrappers, and return-type handling to normalize ResourceResult vs CreateTaskResult behavior.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description is comprehensive and addresses the core changes, including examples and key modifications. However, the Contributors/Review Checklists are unchecked, indicating incomplete documentation. Complete all checklist items in the Contributors Checklist and Review Checklist sections to confirm testing, documentation updates, and self-review have been performed.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding a task_meta parameter to read_resource() for explicit control over task execution.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-resource-task-meta-parameter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/fastmcp/server/providers/fastmcp_provider.py (1)

290-319: Consider prefixing uri with underscore to indicate intentional non-use.

The static analysis tool correctly identifies that uri is unused in FastMCPProviderResourceTemplate._read(). The method expands the original URI from _original_uri_template and params instead of using the passed uri. This is intentional since uri is the external/transformed URI while the child server needs the internal URI.

To clarify intent and silence the linter, prefix with underscore.

🔎 Proposed fix
     @overload
     async def _read(
-        self, uri: str, params: dict[str, Any], task_meta: None = None
+        self, _uri: str, params: dict[str, Any], task_meta: None = None
     ) -> ResourceResult: ...

     @overload
     async def _read(
-        self, uri: str, params: dict[str, Any], task_meta: TaskMeta
+        self, _uri: str, params: dict[str, Any], task_meta: TaskMeta
     ) -> mcp.types.CreateTaskResult: ...

     async def _read(
-        self, uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None
+        self, _uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None
     ) -> ResourceResult | mcp.types.CreateTaskResult:
src/fastmcp/resources/template.py (1)

317-360: Consider prefixing uri with underscore to indicate intentional non-use.

Similar to the base class, the uri parameter is unused in FunctionResourceTemplate._read() because this optimized implementation calls read(arguments=params) directly instead of creating an ephemeral resource. The static analysis tool correctly flags this.

Additionally, note that the task_meta enrichment and check_background_task logic (lines 348-356) is duplicated from the base ResourceTemplate._read(). This is acceptable here since FunctionResourceTemplate intentionally overrides the entire method for optimization (skipping ephemeral resource creation).

🔎 Proposed fix to silence linter
     @overload
     async def _read(
-        self, uri: str, params: dict[str, Any], task_meta: None = None
+        self, _uri: str, params: dict[str, Any], task_meta: None = None
     ) -> ResourceResult: ...

     @overload
     async def _read(
-        self, uri: str, params: dict[str, Any], task_meta: TaskMeta
+        self, _uri: str, params: dict[str, Any], task_meta: TaskMeta
     ) -> mcp.types.CreateTaskResult: ...

     async def _read(
-        self, uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None
+        self, _uri: str, params: dict[str, Any], task_meta: TaskMeta | None = None
     ) -> ResourceResult | mcp.types.CreateTaskResult:
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf1c222 and 2511cff.

⛔ Files ignored due to path filters (9)
  • tests/resources/test_function_resources.py is excluded by none and included by none
  • tests/resources/test_resource_template.py is excluded by none and included by none
  • tests/resources/test_resources.py is excluded by none and included by none
  • tests/server/middleware/test_middleware.py is excluded by none and included by none
  • tests/server/providers/test_local_provider_resources.py is excluded by none and included by none
  • tests/server/tasks/test_resource_task_meta_parameter.py is excluded by none and included by none
  • tests/server/test_dependencies.py is excluded by none and included by none
  • tests/server/test_mount.py is excluded by none and included by none
  • tests/server/test_providers.py is excluded by none and included by none
📒 Files selected for processing (4)
  • src/fastmcp/resources/resource.py
  • src/fastmcp/resources/template.py
  • src/fastmcp/server/providers/fastmcp_provider.py
  • src/fastmcp/server/server.py
🧰 Additional context used
📓 Path-based instructions (1)
src/fastmcp/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/resources/resource.py
  • src/fastmcp/server/server.py
  • src/fastmcp/resources/template.py
  • src/fastmcp/server/providers/fastmcp_provider.py
🧠 Learnings (1)
📚 Learning: 2025-12-25T15:53:07.646Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.646Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required

Applied to files:

  • src/fastmcp/resources/resource.py
  • src/fastmcp/resources/template.py
  • src/fastmcp/server/providers/fastmcp_provider.py
🧬 Code graph analysis (3)
src/fastmcp/resources/resource.py (2)
src/fastmcp/server/tasks/config.py (2)
  • TaskConfig (41-120)
  • TaskMeta (25-37)
src/fastmcp/server/tasks/routing.py (1)
  • check_background_task (27-93)
src/fastmcp/server/server.py (6)
src/fastmcp/server/low_level.py (1)
  • read_resource (204-246)
src/fastmcp/server/middleware/tool_injection.py (1)
  • read_resource (98-103)
src/fastmcp/server/context.py (1)
  • read_resource (313-327)
src/fastmcp/resources/resource.py (5)
  • ResourceResult (117-205)
  • _read (304-304)
  • _read (307-307)
  • _read (309-345)
  • to_mcp_result (192-205)
src/fastmcp/server/tasks/config.py (1)
  • TaskMeta (25-37)
src/fastmcp/resources/template.py (6)
  • _read (181-183)
  • _read (186-188)
  • _read (190-230)
  • _read (318-320)
  • _read (323-325)
  • _read (327-360)
src/fastmcp/resources/template.py (3)
src/fastmcp/server/tasks/config.py (2)
  • TaskConfig (41-120)
  • TaskMeta (25-37)
src/fastmcp/resources/resource.py (5)
  • _read (304-304)
  • _read (307-307)
  • _read (309-345)
  • ResourceResult (117-205)
  • key (372-374)
src/fastmcp/server/tasks/routing.py (1)
  • check_background_task (27-93)
🪛 Ruff (0.14.10)
src/fastmcp/resources/template.py

328-328: Unused method argument: uri

(ARG002)

src/fastmcp/server/providers/fastmcp_provider.py

301-301: Unused method argument: uri

(ARG002)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests with lowest-direct dependencies
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
🔇 Additional comments (13)
src/fastmcp/server/server.py (5)

1224-1248: LGTM! Well-structured overloads for type-safe return types.

The overload pattern correctly narrows the return type based on task_meta:

  • task_meta: NoneResourceResult
  • task_meta: TaskMetaCreateTaskResult

This eliminates the need for runtime isinstance() checks at call sites, which aligns with the PR objectives.


1270-1274: Clear and accurate documentation of the fn_key enrichment strategy.

The comment correctly explains why fn_key enrichment happens in component _read() methods rather than here — resources use the concrete URI while templates use the pattern. This design decision is consistent with the implementations in resource.py and template.py.


1285-1292: Correct middleware chain propagation of task_meta.

The task_meta is correctly passed through the middleware chain via the call_next lambda, ensuring that task routing happens after middleware execution.


1300-1323: Proper task_meta delegation to resource and template _read() methods.

Both concrete resources and templates now receive task_meta via keyword argument, enabling consistent background task routing throughout the resource reading pipeline.


1604-1619: Correctly refactored to use explicit task_meta parameter.

The handler now:

  1. Extracts task metadata from MCP request context (lines 1606-1611)
  2. Leaves fn_key as None for component-level enrichment (line 1604 comment)
  3. Passes task_meta explicitly to read_resource() (line 1615)
  4. Handles both ResourceResult and CreateTaskResult appropriately (lines 1617-1619)

This replaces the previous contextvar-based approach with explicit parameter passing.

src/fastmcp/resources/resource.py (3)

8-8: LGTM! Correct import addition.

The overload import from typing is required for the new type-narrowing overloads.


30-31: Correct import of TaskMeta.

TaskMeta is appropriately imported alongside TaskConfig from the tasks config module.


303-345: Well-implemented _read() with task routing and type-safe overloads.

The implementation correctly:

  1. Uses overloads to narrow return type based on task_meta presence
  2. Enriches task_meta.fn_key with self.key when missing (line 335)
  3. Passes arguments=None to check_background_task since resources don't have arguments
  4. Falls back to synchronous execution when no background task is scheduled

One observation: creating a new TaskMeta instance for enrichment (line 335) is appropriate since TaskMeta is a dataclass and we want to avoid mutating the caller's instance.

src/fastmcp/server/providers/fastmcp_provider.py (2)

16-16: LGTM! Correct import addition.

The overload import is required for the new type-narrowing overloads on _read methods.


151-173: Correct delegation with fn_key enrichment for nested mounts.

The implementation correctly:

  1. Enriches fn_key with self.key (the parent's namespaced URI) before delegation
  2. Delegates to the child server's read_resource() with the original URI
  3. Uses overloads for type-safe return types

This ensures that when components are mounted with namespaces, the Docket lookup key matches the registration key from get_tasks().

src/fastmcp/resources/template.py (3)

8-8: LGTM! Correct import addition.

The overload import from typing is required for the new type-narrowing overloads.


26-26: Correct import of TaskMeta.

TaskMeta is appropriately imported alongside TaskConfig.


180-230: Well-implemented _read() with proper task routing.

The implementation correctly:

  1. Uses overloads for type-safe return types
  2. Enriches fn_key with self.key (the template pattern) when missing
  3. Passes params as arguments to check_background_task for template execution
  4. Falls back to synchronous execution via create_resource() + read()

The comment on line 227 correctly explains why resource.read() is called instead of resource._read() — to avoid double task routing on the ephemeral resource.

- MCP routes no longer set fn_key - components enrich it with self.key
- Provider wrappers set fn_key to parent's namespaced key before delegating
- Use dataclasses.replace() instead of creating new TaskMeta instances
- Add tests for mounted components with task_meta parameter
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/fastmcp/server/providers/fastmcp_provider.py (1)

192-196: Minor inconsistency: Use replace() instead of constructor for consistency.

Lines 193-194 construct a new TaskMeta directly instead of using dataclasses.replace() like the other wrappers (FastMCPProviderTool._run() at line 119, FastMCPProviderResourceTemplate._read() at line 340). While functionally equivalent, using replace() maintains pattern consistency across the codebase.

Suggested change
         # Enrich fn_key with parent's URI before delegating to child
         if task_meta is not None and task_meta.fn_key is None:
-            task_meta = TaskMeta(ttl=task_meta.ttl, fn_key=self.key)
+            task_meta = replace(task_meta, fn_key=self.key)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2511cff and 13f7000.

⛔ Files ignored due to path filters (1)
  • tests/server/tasks/test_task_mount.py is excluded by none and included by none
📒 Files selected for processing (5)
  • src/fastmcp/resources/resource.py
  • src/fastmcp/resources/template.py
  • src/fastmcp/server/providers/fastmcp_provider.py
  • src/fastmcp/server/server.py
  • src/fastmcp/tools/tool.py
🧰 Additional context used
📓 Path-based instructions (1)
src/fastmcp/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/fastmcp/**/*.py: Python ≥ 3.10 with full type annotations required
Prioritize readable, understandable code - clarity over cleverness. Avoid obfuscated or confusing patterns even if shorter
Follow existing patterns and maintain consistency in code implementation
Be intentional about re-exports - don't blindly re-export everything to parent namespaces. Core types defining a module's purpose should be exported. Specialized features can live in submodules. Only re-export to fastmcp.* for most fundamental types
Never use bare except - be specific with exception types

Files:

  • src/fastmcp/resources/template.py
  • src/fastmcp/server/server.py
  • src/fastmcp/server/providers/fastmcp_provider.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/tools/tool.py
🧠 Learnings (1)
📚 Learning: 2025-12-25T15:53:07.646Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-25T15:53:07.646Z
Learning: Applies to src/fastmcp/**/*.py : Python ≥ 3.10 with full type annotations required

Applied to files:

  • src/fastmcp/resources/template.py
  • src/fastmcp/server/providers/fastmcp_provider.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/tools/tool.py
🧬 Code graph analysis (3)
src/fastmcp/resources/template.py (3)
src/fastmcp/server/tasks/config.py (1)
  • TaskMeta (25-37)
src/fastmcp/resources/resource.py (5)
  • _read (305-305)
  • _read (308-308)
  • _read (310-346)
  • ResourceResult (118-206)
  • key (373-375)
src/fastmcp/server/tasks/routing.py (1)
  • check_background_task (27-93)
src/fastmcp/resources/resource.py (4)
src/fastmcp/server/context.py (1)
  • fastmcp (169-174)
src/fastmcp/server/tasks/config.py (2)
  • TaskConfig (41-120)
  • TaskMeta (25-37)
src/fastmcp/resources/template.py (7)
  • _read (182-184)
  • _read (187-189)
  • _read (191-231)
  • _read (319-321)
  • _read (324-326)
  • _read (328-361)
  • key (279-281)
src/fastmcp/server/tasks/routing.py (1)
  • check_background_task (27-93)
src/fastmcp/tools/tool.py (3)
src/fastmcp/server/providers/fastmcp_provider.py (3)
  • _run (90-94)
  • _run (97-101)
  • _run (103-123)
src/fastmcp/server/tasks/config.py (1)
  • TaskMeta (25-37)
src/fastmcp/server/tasks/routing.py (1)
  • check_background_task (27-93)
🪛 Ruff (0.14.10)
src/fastmcp/resources/template.py

329-329: Unused method argument: uri

(ARG002)

src/fastmcp/server/providers/fastmcp_provider.py

324-324: Unused method argument: uri

(ARG002)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run tests: Python 3.10 on ubuntu-latest
  • GitHub Check: Run tests: Python 3.10 on windows-latest
  • GitHub Check: Run tests: Python 3.13 on ubuntu-latest
  • GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (8)
src/fastmcp/tools/tool.py (1)

281-335: LGTM! Clean implementation of task_meta overloads and fn_key enrichment.

The overloads correctly narrow return types based on task_meta presence, and the fn_key enrichment pattern using dataclasses.replace() is consistent with the approach in provider wrappers. The early return when task_result is truthy ensures background tasks short-circuit synchronous execution.

src/fastmcp/resources/resource.py (1)

304-346: LGTM! Consistent task_meta handling for resources.

The overload structure and fn_key enrichment logic mirror the Tool implementation. Passing arguments=None is correct since resources don't have arguments (unlike templates which pass params).

src/fastmcp/resources/template.py (1)

318-361: LGTM! Optimized template read with consistent task_meta handling.

The overloads and fn_key enrichment logic are consistent with ResourceTemplate._read(). The unused uri parameter (flagged by Ruff) is intentional—it maintains signature compatibility with the base class ResourceTemplate._read(), which uses it when creating the ephemeral resource. This subclass skips resource creation for efficiency but must preserve the interface.

src/fastmcp/server/server.py (3)

1223-1264: LGTM! Well-designed overloads with clear documentation.

The overloads provide proper type narrowing, and the docstring clearly explains the return type semantics. The comment at lines 1269-1273 is valuable—it explains why fn_key enrichment is delegated to components rather than done here, since resources and templates use different key formats.


1559-1572: LGTM! Clean extraction of task metadata from MCP context.

The approach of extracting only ttl and leaving fn_key unset is correct—it allows downstream components (or provider wrappers) to enrich with the appropriate key. The explanatory comment clarifies the design decision well.


1603-1624: LGTM! Consistent task_meta handling in read_resource MCP handler.

The pattern mirrors _call_tool_mcp: extract task_meta from context, pass it through, and let the component handle fn_key enrichment. The result type check at line 1622 correctly handles both sync and task paths.

src/fastmcp/server/providers/fastmcp_provider.py (2)

89-123: LGTM! Provider tool wrapper correctly enriches fn_key.

The overloads and enrichment logic are consistent with the pattern used in Tool._run(). Enriching with self.key (the parent's namespaced tool name) ensures Docket uses the correctly registered lookup key.


313-342: LGTM! Template wrapper correctly enriches fn_key with parent's namespaced pattern.

The overloads and enrichment logic are consistent. The unused uri parameter (Ruff ARG002) is required for interface compatibility with the base class ResourceTemplate._read() signature—this is not a defect.

@jlowin
Copy link
Copy Markdown
Member Author

jlowin commented Dec 26, 2025

Use replace() instead of constructor for consistency
will fix in next PR

@jlowin jlowin merged commit fc19f1f into main Dec 26, 2025
13 checks passed
@jlowin jlowin deleted the add-resource-task-meta-parameter branch December 26, 2025 17:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement to existing functionality. For issues and smaller PR improvements. server Related to FastMCP server implementation or server-side functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant